#!/usr/bin/python
#
# Copyright (c) 2011-2012, Peter Dornbach.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
#     * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#     * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following disclaimer
# in the documentation and/or other materials provided with the
# distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

"""
Outputs the result of the optimizer.
"""

import cgi
import textwrap
import lfxml

import wanted_list
from gflags import FLAGS

MAX_CHARS=80

CSS = """
body {
  font-family: sans-serif;
}
td {
  padding-left: 15px;
}
pre {
  font-size: 70%;
}
.head {
  font-weight: bold;
}
.rightalign {
  text-align: right;
}
.wanted {
  vertical-align: top;
}
.unselected {
  color: #808080;
  background-color: #c0c0c0;
}
"""

JAVASCRIPT="""
function update(inputField, tagName) {
  var elements = document.getElementsByName(tagName);
  var trimmed = inputField.value.replace(/^\s+|\s+$/g, '');
  var html = '';
  if (trimmed) {
    html = '&lt;WANTEDLISTID&gt;' + trimmed + '&lt;/WANTEDLISTID&gt;';
  }
  for (var i=0; i<elements.length; i++) {
    elements[i].innerHTML = html;
  }
}
"""

HTML_SKELETON = """
<html>
<head>
<title>%s</title>
<style type="text/css">
%s
</style>
<script type="text/javascript">
%s
</script>
</head>
<body>
<h3>%s</h3>
<h4>Total cost</h4>
<table>
%s
</table>
<h4>Orders</h4>
<table>
%s
</table>
<h4>Shops considered</h4>
<table>
%s
</table>
<br/>
Generated by <a href="http://code.google.com/p/bltools">bltools</a>.
</body>
</html>
"""

TOTAL_SKELETON = """
<td>Number of parts needed:</td>
<td class="rightalign">%d</td>
</tr>
<tr>
<td>Number of bricks needed:</td>
<td class="rightalign">%d</td>
</tr>
<tr>
<td>Number of shops:</td>
<td class="rightalign">%d</td>
</tr>
<tr>
<td>Net cost (without shipping):</td>
<td class="rightalign"><b>%.2f</b></td>
</tr>
<tr>
<td>Gross cost (shipping fee %.2f / shop):</td>
<td class="rightalign"><b>%.2f</b></td>
</tr>
<tr>
<tr>
<td>Number of parts on order:</td>
<td class="rightalign">%d</td>
</tr>
<tr>
<td>Number of bricks on order:</td>
<td class="rightalign">%d</td>
</tr>
"""

ORDER_SHOPHEAD = """
<tr class="head">
<td colspan="7">%s</td>
</tr>
<tr>
<td colspan="6">Number of parts needed:</td>
<td class="rightalign">%d</td>
</tr>
<tr>
<td colspan="6">Number of bricks needed:</td>
<td class="rightalign">%d</td>
</tr>
<tr>
<td colspan="6">Net cost (without shipping):</td>
<td class="rightalign"><b>%.2f</b></td>
</tr>
<tr>
<td colspan="6">Gross cost (with shipping):</td>
<td class="rightalign"><b>%.2f</b></td>
<td colspan="2">Update wanted list ID:
<input type="text" onchange="update(this, 'wanted_%s')"/>
</td>
</tr>
"""

ORDER_ROWHEAD = """
<tr class="head">
<td>Part-ID</td>
<td>Color</td>
<td class="rightalign">Cond.</td>
<td class="rightalign">shops</td>
<td class="rightalign">Qty<br/>needed</td>
<td class="rightalign">Qty<br/>ordered</td>
<td class="rightalign">Unit<br/>price</td>
<td class="rightalign">Total<br/>price</td>
<td>Wanted list XML</td>
</tr>
"""

ORDER_XML = """
<tr>
<td colspan="8"></td>
<td class="wanted" rowspan="%d"><pre>%s</pre></td>
</tr>
"""

ORDER_XML_DYNAMIC = '<span name="wanted_%s"></span>'

ORDER_ROW = """
<tr>
<td>%s</td>
<td>%s</td>
<td>%s</td>
<td class="rightalign">%d</td>
<td class="rightalign">%d</td>
<td class="rightalign">%d</td>
<td class="rightalign">%.2f</td>
<td class="rightalign">%.2f</td>
</tr>
"""

ORDER_SEPARATOR = """
<tr><td>&nbsp;</td></tr>
"""

CONSIDERED_HEAD = """
<tr class="head">
<td>Shop</td>
<td>Critical?</td>
<td>Score</td>
</tr>
"""

CONSIDERED_ROW = """
<tr>
<td>%s</td>
<td>%s</td>
<td>%s</td>
</tr>
"""

UNSELECTED_ROW = """
<tr class="unselected">
<td>%s</td>
<td>%s</td>
<td>%s</td>
</tr>
"""

SHOP_LINK = 'http://www.bricklink.com/store.asp?p=%s'
CATALOG_LINK = 'http://www.bricklink.com/catalogItem.asp?P=%s&colorID=%s'

PART_LINK = 'http://www.bricklink.com/search.asp?Q=%s&colorID=%s'
PART_LINK_NOCOLOR = 'http://www.bricklink.com/search.asp?Q=%s'
PART_COND = '&invNew=%s'

def LeftPad(v, width):
  s = str(v)
  return ' ' * (width - len(s)) + s

def RightPad(v, width):
  s = str(v)
  return s + ' ' * (width - len(s))

def MakeLink(url, text):
  return '<a href="%s">%s</a>' % (url, text)

def PrintListText(list):
  s = ', '.join(str(e) for e in sorted(list))
  for l in textwrap.wrap(s, MAX_CHARS - 1):
    print ' %s' % (l)

def PrintShopsText(optimizer):
  print 'Critical shops:'
  PrintListText(optimizer.CriticalShops().keys())
  print 'Supplemental shops:'
  PrintListText(optimizer.SupplementalShops().keys())

def PrintOrdersText(optimizer, shop_fix_cost):
  orders = optimizer.Orders()
  if (orders == None):
    if (FLAGS.max_shops < FLAGS.consider_shops):
      print "No possible orders found. Try increasing --max_shops."
    else:
      print "No possible orders found - which might be an internal error."
    return
  print 'Orders:'
  total_netto  = 0
  total_brutto = 0
  for shop in sorted(orders, key=optimizer.NetShopTotal, reverse=True):
    shop_total = optimizer.NetShopTotal(shop)
    total_netto  += shop_total
    total_brutto += shop_total + shop_fix_cost
    print ' %s: (Total %s, Gross %s)' % (
        RightPad(shop, 20),
        LeftPad('%.2f' % shop_total, 8),
        LeftPad('%.2f' % (shop_total + shop_fix_cost), 8))
    for part in sorted(orders[shop],
                       key=lambda p: optimizer.UnitPrice(shop, p) * orders[shop][p],
                       reverse=True):
      unit_price = optimizer.UnitPrice(shop, part)
      num_bricks = orders[shop][part]
      print '  %-15s: %s (Unit price %s, Total %s)' % (
          RightPad(part, 10),
          LeftPad(num_bricks, 4),
          LeftPad('%.2f' % unit_price, 8),
          LeftPad('%.2f' % (unit_price * num_bricks), 8))
  print "Total: %10.2f, Gross %10.2f, Shops: %3d" % (total_netto, total_brutto, len(orders))

def PrintAllHtml(
    optimizer,
    shop_data,
    shop_fix_cost,
    ldd_file_name,
    output_html_file_name):
  f = open(output_html_file_name, "w")
  try:
    title = 'Orders for %s' % ldd_file_name
    orders = optimizer.Orders()
    if (orders == None):
      return

    orders_fragment = ''
    num_all_part_types = 0
    num_all_parts = 0
    for shop in sorted(orders, key=optimizer.NetShopTotal, reverse=True):
      num_shop_part_types = 0
      num_shop_parts = 0
      shop_fragment = ''
      for part in sorted(orders[shop],
                         key=lambda p: optimizer.UnitPrice(shop, p) * orders[shop][p],
                         reverse=True):
        unit_price = optimizer.UnitPrice(shop, part)
        num_parts = orders[shop][part]
        num_shop_parts += num_parts
        num_all_parts += num_parts
        num_shop_part_types += 1
        num_all_part_types += 1
        used = '&nbsp;'
        this_shop_data = [x for x in shop_data[part] if x['shop_name'] == shop][0]
        imglink = this_shop_data['lotpic']
        if (part.type() == 'P'):
          link = PART_LINK % (part.id(), part.color())
          color_name = lfxml.TRANSLATE_COLORS_BL[int(part.color())][1
                       ].replace(' ', '&nbsp;')
        else:
          link = PART_LINK_NOCOLOR % (part.id())
          color_name == "&nbsp;"
        if (part.condition() in ('U', 'N')):
          used = part.condition()
          link = link + PART_COND % used
        link = MakeLink(link, part.id())
        shop_fragment += ORDER_ROW % (
            '<img width="50%" src="'+imglink+'"><br>'+link,
            color_name,
            used,
            optimizer.NumShopsAvailable(part),
            optimizer.PartsNeeded()[part],
            num_parts,
            unit_price,
            unit_price * num_parts)
            
      shop_cost = optimizer.NetShopTotal(shop)
      orders_fragment += ORDER_SHOPHEAD % (
          MakeLink(SHOP_LINK % shop, shop),
          num_shop_part_types,
          num_shop_parts,
          shop_cost,
          shop_cost + shop_fix_cost,
          shop)
      orders_fragment += ORDER_ROWHEAD
      REPLACE_TEXT = '...REPL...'
      escaped_xml = ORDER_XML % (
          num_shop_part_types + 2,
          cgi.escape(
              wanted_list.WantedList(orders[shop], REPLACE_TEXT)))
      escaped_xml = escaped_xml.replace(
          REPLACE_TEXT,
          ORDER_XML_DYNAMIC % shop)
      orders_fragment += escaped_xml
      orders_fragment += shop_fragment
      orders_fragment += ORDER_SEPARATOR

    total_cost = optimizer.NetGrandTotal()
    total_fragment = TOTAL_SKELETON % (
        len(optimizer.PartsNeeded()),
        optimizer.NumBricksNeeded(),
        len(orders),
        total_cost,
        shop_fix_cost,
        total_cost + shop_fix_cost * len(orders),
        num_all_part_types,
        num_all_parts)

    considered_fragment = CONSIDERED_HEAD
    for shop in sorted(optimizer.CriticalShops()):
      considered_fragment += CONSIDERED_ROW % (
          MakeLink(SHOP_LINK % shop, shop), 'Y', '')
    for shop in sorted(
        optimizer.SupplementalShops(),
        key=lambda shop: optimizer.SupplementalShops()[shop]['score']):
      considered_fragment += CONSIDERED_ROW % (
          MakeLink(SHOP_LINK % shop, shop), '-',
          '%.2f' % -optimizer.SupplementalShops()[shop]['score'])
    for shop in sorted(
        optimizer.UnselectedShops(),
        key=lambda shop: optimizer.UnselectedShops()[shop]['score']):
      considered_fragment += UNSELECTED_ROW % (
          MakeLink(SHOP_LINK % shop, shop), '-',
          '%.2f' % -optimizer.UnselectedShops()[shop]['score'])
    html = HTML_SKELETON % (
        title, CSS, JAVASCRIPT, title,
        total_fragment, orders_fragment, considered_fragment)
    f.write(html)
  finally:
    f.close()
